using System;
using System.Collections.Generic;
using opt;
using CLELCore;
using CLELCore.Assembly.CLELInstructions;
using CLELCore.Assembly.Descriptors;
using CLELCore.Assembly.Descriptors.ClassDescriptors;
using CLELCore.Assembly.MethodHeaderDataSections;
using CLELCore.Assembly.MethodHeaderDataSections.MethodHeaderDataSectionRows;

namespace opt.lib.ConvergePassBIR
{
	public class ConvergePassBIR : IOptimization
	{
		private bool _is_fat;
		private byte _flags;
		private int _section_size;
		private List<Int32> _seh_flags;
		private List<Int32> _seh_try_from;
		private List<Int32> _seh_try_to;
		private List<Int32> _seh_handler_from;
		private List<Int32> _seh_handler_to;
		private List<Int32> _seh_tokenoroffset;

		private int _br_count = 0;
		private int _brfalse_count = 0;
		private int _brtrue_count  = 0;
		private int _beq_count = 0;
		private int _bge_count = 0;
		private int _bgt_count = 0;
		private int _ble_count = 0;
		private int _blt_count = 0;
		private int _bneun_count = 0;
		private int _bgeun_count = 0;
		private int _bgtun_count = 0;
		private int _bleun_count = 0;
		private int _bltun_count = 0;
		private int _leave_count = 0;

		private int _brs_count = 0;
		private int _brfalses_count = 0;
		private int _brtrues_count  = 0;
		private int _beqs_count = 0;
		private int _bges_count = 0;
		private int _bgts_count = 0;
		private int _bles_count = 0;
		private int _blts_count = 0;
		private int _bneuns_count = 0;
		private int _bgeuns_count = 0;
		private int _bgtuns_count = 0;
		private int _bleuns_count = 0;
		private int _bltuns_count = 0;
		private int _leaves_count = 0;

		private int _br_replaced = 0;
		private int _brfalse_replaced = 0;
		private int _brtrue_replaced  = 0;
		private int _beq_replaced = 0;
		private int _bge_replaced = 0;
		private int _bgt_replaced = 0;
		private int _ble_replaced = 0;
		private int _blt_replaced = 0;
		private int _bneun_replaced = 0;
		private int _bgeun_replaced = 0;
		private int _bgtun_replaced = 0;
		private int _bleun_replaced = 0;
		private int _bltun_replaced = 0;
		private int _leave_replaced = 0;

		public String Name
		{
			get
			{
				return "Converge Pass Branch Instruction Replacement";
			}
		}

		public ConvergePassBIR()
		{
		}
		
		private CLELInstruction DoUpdate(CLELInstruction inst,int off)
		{
			byte opcode = inst.Opcode;
			if(opcode == CLELOpcode.BR)
			{
				return new Br(off);
			}
			else if(opcode == CLELOpcode.BRFALSE)
			{
				return new Brfalse(off);
			}
			else if(opcode == CLELOpcode.BRTRUE)
			{
				return new Brtrue(off);
			}
			else if(opcode == CLELOpcode.BEQ)
			{
				return new Beq(off);
			}
			else if(opcode == CLELOpcode.BGE)
			{
				return new Bge(off);
			}
			else if(opcode == CLELOpcode.BGT)
			{
				return new Bgt(off);
			}
			else if(opcode == CLELOpcode.BLE)
			{
				return new Ble(off);
			}
			else if(opcode == CLELOpcode.BLT)
			{
				return new Blt(off);
			}
			else if(opcode == CLELOpcode.BNEUN)
			{
				return new Bneun(off);
			}
			else if(opcode == CLELOpcode.BGEUN)
			{
				return new Bgeun(off);
			}
			else if(opcode == CLELOpcode.BGTUN)
			{
				return new Bgtun(off);
			}
			else if(opcode == CLELOpcode.BLEUN)
			{
				return new Bleun(off);
			}
			else if(opcode == CLELOpcode.BLTUN)
			{
				return new Bltun(off);
			}
			else if(opcode == CLELOpcode.LEAVE)
			{
				return new Leave(off);
			}
			else if(opcode == CLELOpcode.BRS)
			{
				return new Brs((sbyte)off);
			}
			else if(opcode == CLELOpcode.BRFALSES)
			{
				return new Brfalses((sbyte)off);
			}
			else if(opcode == CLELOpcode.BRTRUES)
			{
				return new Brtrues((sbyte)off);
			}
			else if(opcode == CLELOpcode.BEQS)
			{
				return new Beqs((sbyte)off);
			}
			else if(opcode == CLELOpcode.BGES)
			{
				return new Bges((sbyte)off);
			}
			else if(opcode == CLELOpcode.BGTS)
			{
				return new Bgts((sbyte)off);
			}
			else if(opcode == CLELOpcode.BLES)
			{
				return new Bles((sbyte)off);
			}
			else if(opcode == CLELOpcode.BLTS)
			{
				return new Blts((sbyte)off);
			}
			else if(opcode == CLELOpcode.BNEUNS)
			{
				return new Bneuns((sbyte)off);
			}
			else if(opcode == CLELOpcode.BGEUNS)
			{
				return new Bgeuns((sbyte)off);
			}
			else if(opcode == CLELOpcode.BGTUNS)
			{
				return new Bgtuns((sbyte)off);
			}
			else if(opcode == CLELOpcode.BLEUNS)
			{
				return new Bleuns((sbyte)off);
			}
			else if(opcode == CLELOpcode.BLTUNS)
			{
				return new Bltuns((sbyte)off);
			}
			else //if(opcode == CLELOpcode.LEAVES)
			{
				return new Leaves((sbyte)off);
			}
		}

		private CLELInstruction DoReplace(CLELInstruction inst,int offset)
		{
			if(offset >= -128 && offset <= 127)
			{
				byte opcode = inst.Opcode;
				sbyte new_offset = (sbyte)offset;
				if(opcode == CLELOpcode.BR)
				{
					_br_replaced++;
					return new Brs(new_offset);
				}
				else if(opcode == CLELOpcode.BRFALSE)
				{
					_brfalse_replaced++;
					return new Brfalses(new_offset);
				}
				else if(opcode == CLELOpcode.BRTRUE)
				{
					_brtrue_replaced++;
					return new Brtrues(new_offset);
				}
				else if(opcode == CLELOpcode.BEQ)
				{
					_beq_replaced++;
					return new Beqs(new_offset);
				}
				else if(opcode == CLELOpcode.BGE)
				{
					_bge_replaced++;
					return new Bges(new_offset);
				}
				else if(opcode == CLELOpcode.BGT)
				{
					_bgt_replaced++;
					return new Bgts(new_offset);
				}
				else if(opcode == CLELOpcode.BLE)
				{
					_ble_replaced++;
					return new Bles(new_offset);
				}
				else if(opcode == CLELOpcode.BLT)
				{
					_blt_replaced++;
					return new Blts(new_offset);
				}
				else if(opcode == CLELOpcode.BNEUN)
				{
					_bneun_replaced++;
					return new Bneuns(new_offset);
				}
				else if(opcode == CLELOpcode.BGEUN)
				{
					_bgeun_replaced++;
					return new Bgeuns(new_offset);
				}
				else if(opcode == CLELOpcode.BGTUN)
				{
					_bgtun_replaced++;
					return new Bgtuns(new_offset);
				}
				else if(opcode == CLELOpcode.BLEUN)
				{
					_bleun_replaced++;
					return new Bleuns(new_offset);
				}
				else if(opcode == CLELOpcode.BLTUN)
				{
					_bltun_replaced++;
					return new Bltuns(new_offset);
				}
				else if(opcode == CLELOpcode.LEAVE)
				{
					_leave_replaced++;
					return new Leaves(new_offset);
				}
			}
			return null;
		}
		
		private void LoadSEH(List<CLELInstruction> code,MethodHeaderDataSection seh)
		{
			if(seh != null)
			{
				_is_fat = seh.IsFat;
				_flags = seh.Flags;
				_section_size = seh.SectionSize;
				_seh_flags = new List<Int32>();
				_seh_try_from = new List<Int32>();
				_seh_try_to = new List<Int32>();
				_seh_handler_from = new List<Int32>();
				_seh_handler_to = new List<Int32>();
				_seh_tokenoroffset = new List<Int32>();

				Dictionary<Int32,Int32> offsets = new Dictionary<Int32,Int32>();
				int i,count = code.Count;
				int off = 0;
				for(i = 0;i < count;i++)
				{
					offsets.Add(off,i);
					off += code[i].Length;
				}
				List<MethodHeaderDataSectionRow> rows = seh.Rows;
				count = rows.Count;
				int from_try,to_try,from_handler,to_handler;
				SEHDataSectionRow row;
				for(i = 0;i < count;i++)
				{
					row = (SEHDataSectionRow)rows[i];
					from_try = offsets[row.TryOffset];
					to_try = row.TryOffset+row.TryLength;
					to_try = offsets[to_try];

					from_handler = offsets[row.HandlerOffset];
					to_handler = row.HandlerOffset+row.HandlerLength;
					to_handler = offsets[to_handler];
					_seh_flags.Add(row.Flags);
					_seh_try_from.Add(from_try);
					_seh_try_to.Add(to_try);
					_seh_handler_from.Add(from_handler);
					_seh_handler_to.Add(to_handler);
					_seh_tokenoroffset.Add(row.TokenOrOffset);
				}
			}
			else
			{
				_seh_flags = null;
				_seh_try_from = null;
				_seh_try_to = null;
				_seh_handler_from = null;
				_seh_handler_to = null;
				_seh_tokenoroffset = null;
			}
		}

		private MethodHeaderDataSection ResetSEH(List<CLELInstruction> code)
		{
			if(_seh_flags != null)
			{
				Dictionary<Int32,Int32> offsets = new Dictionary<Int32,Int32>();
				int i,count = code.Count;
				int off = 0;
				for(i = 0;i < count;i++)
				{
					offsets.Add(i,off);
					off += code[i].Length;
				}
				count = _seh_try_from.Count;
				int flags,from_try,to_try,from_handler,to_handler,tokenoroffset;
				SEHDataSectionRow row;
				MethodHeaderDataSection sec = new MethodHeaderDataSection(_flags,_section_size);
				for(i = 0;i < count;i++)
				{
					flags = _seh_flags[i];
					tokenoroffset = _seh_tokenoroffset[i];
					from_try = _seh_try_from[i];
					to_try = _seh_try_to[i];
					from_handler = _seh_handler_from[i];
					to_handler = _seh_handler_to[i];

					from_try = offsets[from_try];
					to_try = offsets[to_try] - from_try;
					from_handler = offsets[from_handler];
					to_handler = offsets[to_handler] - from_handler;

					row = new SEHDataSectionRow(flags,from_try,to_try,from_handler,to_handler,tokenoroffset,_is_fat);
					sec.AddRow(row);
				}
				return sec;
			}
			return null;
		}

		private void CountBranch(CLELInstruction inst)
		{
			byte opcode = inst.Opcode;
			if(opcode == CLELOpcode.BR)
			{
				_br_count++;
			}
			else if(opcode == CLELOpcode.BRFALSE)
			{
				_brfalse_count++;
			}
			else if(opcode == CLELOpcode.BRTRUE)
			{
				_brtrue_count++;
			}
			else if(opcode == CLELOpcode.BEQ)
			{
				_beq_count++;
			}
			else if(opcode == CLELOpcode.BGE)
			{
				_bge_count++;
			}
			else if(opcode == CLELOpcode.BGT)
			{
				_bgt_count++;
			}
			else if(opcode == CLELOpcode.BLE)
			{
				_ble_count++;
			}
			else if(opcode == CLELOpcode.BLT)
			{
				_blt_count++;
			}
			else if(opcode == CLELOpcode.BNEUN)
			{
				_bneun_count++;
			}
			else if(opcode == CLELOpcode.BGEUN)
			{
				_bgeun_count++;
			}
			else if(opcode == CLELOpcode.BGTUN)
			{
				_bgtun_count++;
			}
			else if(opcode == CLELOpcode.BLEUN)
			{
				_bleun_count++;
			}
			else if(opcode == CLELOpcode.BLTUN)
			{
				_bltun_count++;
			}
			else if(opcode == CLELOpcode.LEAVE)
			{
				_leave_count++;
			}
			else if(opcode == CLELOpcode.BRS)
			{
				_brs_count++;
			}
			else if(opcode == CLELOpcode.BRFALSES)
			{
				_brfalses_count++;
			}
			else if(opcode == CLELOpcode.BRTRUES)
			{
				_brtrues_count++;
			}
			else if(opcode == CLELOpcode.BEQS)
			{
				_beqs_count++;
			}
			else if(opcode == CLELOpcode.BGES)
			{
				_bges_count++;
			}
			else if(opcode == CLELOpcode.BGTS)
			{
				_bgts_count++;
			}
			else if(opcode == CLELOpcode.BLES)
			{
				_bles_count++;
			}
			else if(opcode == CLELOpcode.BLTS)
			{
				_blts_count++;
			}
			else if(opcode == CLELOpcode.BNEUNS)
			{
				_bneuns_count++;
			}
			else if(opcode == CLELOpcode.BGEUNS)
			{
				_bgeuns_count++;
			}
			else if(opcode == CLELOpcode.BGTUNS)
			{
				_bgtuns_count++;
			}
			else if(opcode == CLELOpcode.BLEUNS)
			{
				_bleuns_count++;
			}
			else if(opcode == CLELOpcode.BLTUNS)
			{
				_bltuns_count++;
			}
			else if(opcode == CLELOpcode.LEAVES)
			{
				_leaves_count++;
			}
		}

		private bool ProcessMethod(List<CLELInstruction> code)
		{
			bool changed = false;
			int i,code_count = code.Count;
			Dictionary<Int32,Int32> offset = new Dictionary<Int32,Int32>();
			int off = 0;
			for(i = 0;i < code_count;i++)
			{
				offset.Add(off,i);
				off += code[i].Length;
			}
			Dictionary<Int32,Int32> index_to_index = new Dictionary<Int32,Int32>();
			Dictionary<Int32,List<Int32>> switches = new Dictionary<Int32,List<Int32>>();
			int branch_offset = 0;
			int branch_to_index;
			off = 0;
			Switch sw;
			List<Int32> switch_offsets;
			int j,switch_offsets_count;
			List<Int32> switch_indexes;
			for(i = 0;i < code_count;i++)
			{
				if(BranchUtilities.GetBranchOffset(code[i],ref branch_offset))
				{
					CountBranch(code[i]);
					branch_to_index = offset[off+code[i].Length+branch_offset];
					index_to_index.Add(i,branch_to_index);
				}
				else if(code[i].Opcode == CLELOpcode.SWITCH)
				{
					sw = (Switch)code[i];
					switch_offsets = sw.Params;
					switch_offsets_count = switch_offsets.Count;
					switch_indexes = new List<Int32>();
					for(j = 0;j < switch_offsets_count;j++)
					{
						switch_indexes.Add(offset[off+code[i].Length+switch_offsets[j]]);
					}
					switches.Add(i,switch_indexes);
				}
				off += code[i].Length;
			}

			//DO ALL REPLACES
			CLELInstruction current,replace;
			for(i = 0;i < code_count;i++)
			{
				current = code[i];
				if(BranchUtilities.GetBranchOffset(current,ref branch_offset))
				{
					replace = DoReplace(current,branch_offset);
					if(replace != null)
					{
						changed = true;
						code[i] = replace;
					}
				}
			}

			off = 0;
			offset = new Dictionary<Int32,Int32>();
			for(i = 0;i < code_count;i++)
			{
				offset.Add(i,off);
				off += code[i].Length;
			}
			off = 0;
			for(i = 0;i < code_count;i++)
			{
				if(BranchUtilities.IsBranch(code[i]))
				{
					code[i] = DoUpdate(code[i],offset[index_to_index[i]]-off-code[i].Length);
				}
				else if(code[i].Opcode == CLELOpcode.SWITCH)
				{
					switch_indexes = switches[i];
					switch_offsets = new List<Int32>();
					switch_offsets_count = switch_indexes.Count;
					for(j = 0;j < switch_offsets_count;j++)
					{
						switch_offsets.Add(offset[switch_indexes[j]]-off-code[i].Length);
					}
					code[i] = new Switch(switch_offsets);
				}
				off += code[i].Length;
			}
			return changed;
		}

		public void DoOptimization(CLEL clel,CLELLogger log)
		{
			log.WriteLine("Converge Pass BIR: " + clel.Path+"\n");
			
			List<ClassDescriptor> class_descriptors = clel.GetInternalClasses();
			List<MethodDescriptor> method_descriptors;
			int i,class_count = class_descriptors.Count;
			int j,method_count;
			ClassDescriptor cd;
			TypeDefClassDescriptor tycd;
			MethodDescriptor md;
			List<CLELInstruction> code;
			bool code_changed;
			bool update_code;
			for(i = 0;i < class_count;i++)
			{
				cd = class_descriptors[i];
				if(cd.Type == ClassDescriptor.TYPEDEF_CLASSDESCRIPTOR)
				{
					tycd = (TypeDefClassDescriptor)cd;
					method_descriptors = tycd.Methods;
					method_count = method_descriptors.Count;
					for(j = 0;j < method_count;j++)
					{
						md = method_descriptors[j];
						code = md.GetMethodsCode();
						if(code != null)
						{
							MethodHeaderDataSection seh = md.SEHDataSection;
							LoadSEH(code,seh);
							update_code = ProcessMethod(code);
							code_changed = update_code;
							while(code_changed)
							{
								code_changed = ProcessMethod(code);
							}

							if(update_code)
							{
								md.SetMethodsCode(code,null);
							}
							md.SEHDataSection = ResetSEH(code);
						}
					}
				}
			}

			//write log
			log.WriteLine("Totals");
			log.WriteLine("BR: "+_br_count);
			log.WriteLine("BRFALSE: "+_brfalse_count);
			log.WriteLine("BRTRUE: "+_brtrue_count);
			log.WriteLine("BEQ: "+_beq_count);
			log.WriteLine("BGE: "+_bge_count);
			log.WriteLine("BGT: "+_bgt_count);
			log.WriteLine("BLE: "+_ble_count);
			log.WriteLine("BLT: "+_blt_count);
			log.WriteLine("BNEUN: "+_bneun_count);
			log.WriteLine("BGEUN: "+_bgeun_count);
			log.WriteLine("BGTUN: "+_bgtun_count);
			log.WriteLine("BLEUN: "+_bleun_count);
			log.WriteLine("BLTUN: "+_bltun_count);
			log.WriteLine("LEAVE: "+_leave_count);
			log.WriteLine("BRS: "+_brs_count);
			log.WriteLine("BRFALSES: "+_brfalses_count);
			log.WriteLine("BRTRUES: "+_brtrues_count);
			log.WriteLine("BEQS: "+_beqs_count);
			log.WriteLine("BGES: "+_bges_count);
			log.WriteLine("BGTS: "+_bgts_count);
			log.WriteLine("BLES: "+_bles_count);
			log.WriteLine("BLTS: "+_blts_count);
			log.WriteLine("BNEUNS: "+_bneuns_count);
			log.WriteLine("BGEUNS: "+_bgeuns_count);
			log.WriteLine("BGTUNS: "+_bgtuns_count);
			log.WriteLine("BLEUNS: "+_bleuns_count);
			log.WriteLine("BLTUNS: "+_bltuns_count);
			log.WriteLine("LEAVES: "+_leaves_count);
			
			log.WriteLine("\nReplaced");
			log.WriteLine("BR: "+_br_replaced);
			log.WriteLine("BRFALSE: "+_brfalse_replaced);
			log.WriteLine("BRTRUE: "+_brtrue_replaced);
			log.WriteLine("BEQ: "+_beq_replaced);
			log.WriteLine("BGE: "+_bge_replaced);
			log.WriteLine("BGT: "+_bgt_replaced);
			log.WriteLine("BLE: "+_ble_replaced);
			log.WriteLine("BLT: "+_blt_replaced);
			log.WriteLine("BNEUN: "+_bneun_replaced);
			log.WriteLine("BGEUN: "+_bgeun_replaced);
			log.WriteLine("BGTUN: "+_bgtun_replaced);
			log.WriteLine("BLEUN: "+_bleun_replaced);
			log.WriteLine("BLTUN: "+_bltun_replaced);
			log.WriteLine("LEAVE: "+_leave_replaced+"\n");
		}
	}
}
